package service

import (
	"alan-snippet/internal/dao"
	"alan-snippet/internal/dto"
	"alan-snippet/internal/models"
	"alan-snippet/pkg/erru"
	"context"
	"errors"
	"strconv"

	"gorm.io/gorm"
)

func CreateSnippet(snippet dto.CreateSnippetRequestDTO, userId uint) (*models.Snippet, error) {

	tags, err := dao.FindOrCreateTagsByNames(snippet.Tags)
	if err != nil {
		return nil, err
	}

	newSnippet := &models.Snippet{
		Title:   snippet.Title,
		Content: snippet.Content,
		Tags:    tags,
		UserID:  userId,
	}

	// GORM 在这里会施展魔法：
	// - 开启一个事务
	// - 插入一条记录到 `snippets` 表
	// - 遍历 snippet.Tags，为每个 tag 在 `snippet_tags` 中间表创建一条关联记录
	// - 提交事务
	err = dao.CreateSnippet(newSnippet)
	if err != nil {
		return nil, err
	}

	return newSnippet, nil
}

func ListSnippet(listDTO dto.ListSnippetsRequestDTO) ([]models.Snippet, int64, error) {
	return dao.ListSnippet(listDTO)
}

func GetSnippetById(id int) (*models.Snippet, error) {
	snippet, err := dao.GetSnippetById(id)
	if err == nil {
		// use Gorutine to handle Redis
		go func() {
			ctx := context.Background()
			// 1. 设置浏览量
			// key为 snippet:view_count:id
			dao.RedisClient.Incr(ctx, "snippet:view_count:"+strconv.Itoa(id))

			// 2. 更新排行榜分数
			// ZSet 的 key 可以是 "snippet:ranking"
			// member 是 snippet 的 ID
			// score 是 1 (每次加1)
			dao.RedisClient.ZIncrBy(ctx, "snippet:ranking", 1.0, strconv.Itoa(id))
		}()
	}
	return snippet, nil
}

func GetPopularSnippets() ([]*models.Snippet, error) {
	// 1. 从 Redis 的 Sorted Set 中获取 Top 10 的 ID
	// key: "snippet:ranking"
	// start: 0, stop: 9 (前10个)
	// 命令: ZREVRANGE (因为分数越高越靠前，所以是逆序)
	ctx := context.Background()
	ids, err := dao.RedisClient.ZRevRange(ctx, "snippet:ranking", 0, 9).Result()
	if err != nil {
		return nil, err
	}

	// 2. 根据这些 IDs，去 MySQL 中批量查询 Snippet 的详细信息
	// GORM 支持 `db.Where("id IN ?", ids).Find(&snippets)`
	return dao.GetPopularSnippets(ids)
}

func UpdateSnippet(snippetId string, updateDto *dto.UpdateSnippetRequestDTO, currentUserId uint) (*models.Snippet, error) {

	// 1.根据id查出snippet
	// 2.确认是不是userId的
	// 3.更新snippet

	id, _ := strconv.Atoi(snippetId)
	snippet, err := dao.GetSnippetById(id)
	if err != nil {
		// 如果 GORM 返回 ErrRecordNotFound，将其包装成我们自己的业务错误
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, erru.ErrResourceNotFound
		}
		// 其他数据库错误，包装成内部服务错误
		return nil, erru.ErrInternalServer.Wrap(err)
	}
	userId := snippet.UserID
	if userId != currentUserId {
		return nil, erru.ErrUnauthorized
	}
	// 查出tags
	tags := make([]*models.Tag, 0, len(updateDto.Tags))
	for _, tagName := range updateDto.Tags {
		tag, err := dao.FindOrCreateTag(tagName)
		if err != nil {
			if errors.Is(err,gorm.ErrRecordNotFound) {
				return nil,erru.ErrResourceNotFound.Wrap(err)
			}
			return nil, erru.ErrInternalServer
		}
		tags = append(tags, tag)
	}
	// 封装snippet
	// 为什么错误，缺少了ID，没法更新
	// snippet = &models.Snippet{
	// 	Title:   updateDto.Title,
	// 	Content: updateDto.Content,
	// 	Tags:    tags,
	// 	UserID:  currentUserId,
	// }
	{
		snippet.Title = updateDto.Title
		snippet.Content = updateDto.Content
		snippet.Tags = tags
	}
	// 更新
	snippet, err = dao.UpdateSnippet(id, snippet)
	if err != nil {
		return nil, err
	}

	return snippet, nil
}

func DeleteSnippet(snippetId int, currentUserId uint) error {
	snippet, err := dao.GetSnippetById(snippetId)
	if err != nil {
		return err
	}
	if snippet.UserID != currentUserId {
		return errors.New("这不是你的snippet")
	}

	return dao.DeleteSnippet(snippetId)
}
